package com.cnbot.aimolib.detect;

import android.graphics.RectF;
import android.os.SystemClock;

import com.aimall.core.define.ImoImageFormat;
import com.aimall.core.define.ImoImageOrientation;
import com.cnbot.aimolib.AimoEngine;
import com.cnbot.aimolib.DetectionTrackerCore;
import com.cnbot.aimolib.bean.AimoFrameData;
import com.cnbot.aimolib.bean.AimoOutlineData;
import com.cnbot.aimolib.bean.AimoRawFaceData;
import com.cnbot.aimolib.db.AimoFaceDbData;
import com.cnbot.aimolib.db.AimoFaceDbManager;
import com.cnbot.aimolib.utils.DetectionTrackerUtils;
import com.white.loglib.L;

import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.ArrayBlockingQueue;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicBoolean;

import static com.cnbot.aimolib.detect.DetectType.STATIC_BMP;


/**
 * @author ytf
 * Created by on 2021/06/05 09:42
 */
public class DetectTask implements Runnable {

    private ArrayBlockingQueue<AimoFrameData> queue;
    private volatile AtomicBoolean isExit = new AtomicBoolean(false);
    private volatile boolean isRunning = false;

    private IDetectInspectStrategy strategy;
    private boolean saveAfterComplete;
    private String savePath;
    private Callback mCallback;
    private ImoImageFormat format;
    private ImoImageOrientation orientation;
    private DetectType detectType;
    private Object[] transExtra;
    private long timeout;
    private RectF strictRect;

    private DetectTask(Builder builder) {
        queue = new ArrayBlockingQueue<>(1);

        strategy = builder.strategy == null ? new DefaultDetectStrategy() : builder.strategy;
        saveAfterComplete = builder.saveAfterDetectComplete;
        savePath = builder.saveAbsPath;
        mCallback = builder.mCallback;
        format = builder.format == null ? ImoImageFormat.IMO_IMAGE_NV21 : builder.format;
        orientation = builder.orientation == null ? ImoImageOrientation.IMO_IMAGE_UP : builder.orientation;
        detectType = builder.type;
        transExtra = builder.transExtra;
        timeout = (long) (builder.timeoutSeconds * 1000);
        strictRect = builder.strictRect;
    }

    public boolean isActive(){
        return isRunning && !isExit.get();
    }

    public void post(AimoFrameData data){
        if(!isRunning) return;

        try {
            boolean ret = queue.offer(data);
            if(!ret){
//                L.e("队列已满，丢弃");
            }
        }catch (Exception e){

        }
    }

    @Override
    public void run() {

        DetectionTrackerCore core = AimoEngine.getInstance().getCore();
        if(!core.canWork()){
            mCallback.onFaceDetectEmpty(transExtra);
            return;
        }

        long start = SystemClock.uptimeMillis();

        RectF preview = new RectF(0f, 0f, 0f, 0f);
        //边缘触发控制
        boolean hasDetectComplete = false;

        while (!isExit.get()){
            isRunning = true;
            try {
                //3秒无数据自动退出
                AimoFrameData data = queue.poll(2, TimeUnit.SECONDS);
                if(data == null){
                    //超时没有数据，该结束了
                    exit();
                    mCallback.onFaceDetectEmpty();
                }else if(data.isValid()){
                    preview.right = data.width;
                    preview.bottom = data.height;

                    List<AimoRawFaceData> ret = null;

                    switch (detectType){
                        case STATIC_BMP:
                            ret = core.detectFromBmp(data.bmp);
                            break;
                        case STATIC_BYTES:
                            ret = core.detectFromStaticBytes(data.data, data.width, data.height, format, orientation);
                            break;
                        case STREAM_BYTES:
                            ret = core.detectFromStreamBytes(data.data, data.width, data.height, format, orientation);
                            break;
                    }
                    //有人脸的其情况平均花费500毫秒
                    if(ret != null && ret.size() > 0){

                        List<AimoOutlineData> outlines = mapPoints(ret);

                        AimoRawFaceData select = strategy.selectFace(ret, outlines, preview, strictRect);
                        if(select != null){

                            if(saveAfterComplete){
                                String path = DetectionTrackerUtils.saveNv21ToLocal(data.data, data.width, data.height, savePath);
                                L.e("检测完成，已保存人像到本地， path = " + path);

                                AimoFaceDbData preInsert = new AimoFaceDbData(null, select.faceFeature.getFeatures(), path, "");
                                long id = AimoFaceDbManager.getInstance()
                                        .insertOrReplace(preInsert);
                                if(id != -1){
                                    preInsert._id = (int)id;
                                }
                                mCallback.onFaceDetectResult(select, preInsert, transExtra);
                            }else{
                                L.e("检测完成");
                                mCallback.onFaceDetectResult(select, null, transExtra);
                            }
                            exit();
                            hasDetectComplete = true;
                        }else{
//                            L.e("不符合人脸筛选条件，丢弃");
                        }
                        mCallback.onFaceOutlineDetected(outlines);
                    }else if(detectType == STATIC_BMP){
                        //如果是单张图片识别，没识别出来直接返回失败
                        mCallback.onFaceDetectEmpty(transExtra);
                        exit();
                    }else{
                        mCallback.onFaceDetectEmpty(transExtra);
                    }
//                    L.e("识别及处理过程花费时间：" + (SystemClock.uptimeMillis() - start) + "ms");

                    if(timeout != 0){
                        if(!hasDetectComplete && SystemClock.uptimeMillis() - start >= timeout){
                            mCallback.onFaceDetectEmpty();
                            exit();
                        }
                    }
                }else{
                    exit();
                }
            }catch (Exception e){
                mCallback.onFaceDetectEmpty(transExtra);
                exit();
            }
        }
        queue.clear();
        L.e("人脸检测线程结束");
    }

    private List<AimoOutlineData> mapPoints(List<AimoRawFaceData> data){
        List<AimoOutlineData> ret = new ArrayList<>();
        for (AimoRawFaceData d : data){
            AimoOutlineData poi = new AimoOutlineData();
            poi.faceId = d.faceInfo.getId();
            poi.mapPoints(d.faceInfo.getPoints());
            poi.mapRect(d.faceInfo.getRect());
            ret.add(poi);
        }
        return ret;
    }

    public void exit(){
        isRunning = false;
        isExit.set(true);
    }

    public interface Callback{

        void onFaceOutlineDetected(List<AimoOutlineData> facePoints);

        void onFaceDetectResult(AimoRawFaceData face, AimoFaceDbData record, Object... transExtra);

        void onFaceDetectEmpty(Object... transExtra);
    }

    public static class Builder{

        private IDetectInspectStrategy strategy;
        private Callback mCallback;
        private String saveAbsPath;
        private boolean saveAfterDetectComplete;
        private ImoImageFormat format;
        private ImoImageOrientation orientation;
        private DetectType type;
        private Object[] transExtra;
        private float timeoutSeconds = 0f;
        private RectF strictRect;

        public Builder setStrategy(IDetectInspectStrategy s){
            strategy = s;
            return this;
        }

        public Builder enableSave(boolean yn){
            this.saveAfterDetectComplete = yn;
            return this;
        }

        public Builder savePath(String path){
            saveAbsPath = path;
            return this;
        }

        public Builder detectType(DetectType type){
            this.type = type;
            return this;
        }

        public Builder transExtra(Object...extra){
            this.transExtra = extra;
            return this;
        }

        public Builder callback(Callback callback){
            mCallback = callback;
            return this;
        }

        public Builder format(ImoImageFormat format){
            this.format = format;
            return this;
        }

        public Builder timeout(float seconds){
            this.timeoutSeconds = seconds;
            return this;
        }

        public Builder strictRect(RectF rect){
            this.strictRect = rect;
            return this;
        }

        public Builder orientation(ImoImageOrientation o){
            this.orientation = o;
            return this;
        }

        public DetectTask build(){
            return new DetectTask(this);
        }
    }
}
